Namespace ALUUnit
    Public Class node
        Public Enum alu_operation As Byte
            MULTIPLY = 0
            ADDITION = 1
            POWEROF = 2
            DIVIDE = 3
            SUBTRACTION = 4
        End Enum
        Public Enum alu_valueType As Byte
            FLOAT = 0
            VARIABLE_LOOKUP = 1
            ALU_REFERENCE = 2
        End Enum

        Private mParentNode As node = Nothing
        Private mBracketNode As node = Nothing
        Private mOperationNode As node = Nothing

        Private mOperation As alu_operation = alu_operation.ADDITION
        Private mValue As Single = 0
        Private mValueVariable As BikSettings.TableVariables = BikSettings.TableVariables.BCTV_UNDEFINED
        Private mVariable As alu_valueType = 0

        Private mCount As Byte = 0
        Private mAluNum As Integer = 0

        Public Sub New(ByRef Bracket As node, ByRef Parent As node, ByRef Operation As node)
            mParentNode = Parent
            mBracketNode = Bracket
            mOperationNode = Operation
        End Sub
        Public Sub New(ByVal equation As String, ByRef position As UInt16, ByRef Parent As node, ByVal Count As Byte)
            mCount = Count
            mParentNode = Parent
            Dim sngTmpMult As Single = 1
            Dim strTmpValue As String = ""

            If position = 0 Then
                ' Make all Upper case
                equation = "(" & equation.ToUpper() & ")"
                ' strip spaces
                equation = equation.Replace(" ", "")

                ' Do initial bracket checks
                Dim nLftBrckt As UInt16 = 0
                Dim nRghtBrcky As UInt16
                Dim nCnt As Integer = 0
                Dim nTmp As UInt16 = 0
                Dim rgxMatches As System.Text.RegularExpressions.MatchCollection
                While nCnt < equation.Length
                    nCnt += 1
                    nCnt = InStr(nCnt, equation, "(")
                    If nCnt <> 0 Then
                        nLftBrckt += 1
                    Else
                        Exit While
                    End If
                End While
                nCnt = 0
                While nCnt < equation.Length
                    nCnt += 1
                    nCnt = InStr(nCnt, equation, ")")
                    If nCnt <> 0 Then
                        nRghtBrcky += 1
                    Else
                        Exit While
                    End If
                End While
                If nRghtBrcky <> nLftBrckt Then
                    Throw New ArathmaticUnit_Exception("Brackets Unmatched")
                End If

                ' Check for invalid characters
                Dim rgxMatch As New System.Text.RegularExpressions.Regex("^([A-Z0-9]|\+|\-|\*|/|\^|\(|\))*$")
                If Not rgxMatch.IsMatch(equation) Then
                    Throw New ArathmaticUnit_Exception("Invalid Characters")
                End If

                ' Check if single bracketed variable/value
                rgxMatch = New System.Text.RegularExpressions.Regex("\([A-Z0-9]*\)")
                If rgxMatch.IsMatch(equation) Then
                    rgxMatches = rgxMatch.Matches(equation)
                    Throw New ArathmaticUnit_Exception("Single Bracketed Value/Variable not allowed", InStr(equation, rgxMatches.Item(0).Value))
                End If

                ' Check if value/variable has not operator next to bracket
                rgxMatch = New System.Text.RegularExpressions.Regex("([A-Z0-9]\(|\)[A-Z0-9])")
                If rgxMatch.IsMatch(equation) Then
                    rgxMatches = rgxMatch.Matches(equation)
                    Throw New ArathmaticUnit_Exception("Bracket and Value without operator", InStr(equation, rgxMatches.Item(0).Value))
                End If

                ' Check if operator is sitting in the middle of nowhere
                rgxMatch = New System.Text.RegularExpressions.Regex("((\+|\-|\*|\/|\^)\)|\((\*|/|\^))")
                If rgxMatch.IsMatch(equation) Then
                    rgxMatches = rgxMatch.Matches(equation)
                    Throw New ArathmaticUnit_Exception("Bracket and operator without value", InStr(equation, rgxMatches.Item(0).Value))
                End If

            End If

start:      If position + 1 < equation.Length And equation <> "" Then

                If isBracket() Then
                    mOperationNode = New node(equation, position, Me, Count + 1)
                    Return
                End If

                If equation.Chars(position) = ")" Then
                    position += 1
                    'GoTo start
                    Return
                Else
                    'get the value here!
                    ' Determine if its a - + * / or ^
                    Select Case equation.Chars(position)
                        Case "*"
                            mOperation = alu_operation.MULTIPLY
                            sngTmpMult = 1
                            position += 1
                        Case "/"
                            mOperation = alu_operation.DIVIDE
                            sngTmpMult = 1
                            position += 1
                        Case "-"
                            mOperation = alu_operation.SUBTRACTION
                            sngTmpMult = -1
                            position += 1
                        Case "+"
                            mOperation = alu_operation.ADDITION
                            sngTmpMult = 1
                            position += 1
                        Case "^"
                            mOperation = alu_operation.POWEROF
                            sngTmpMult = 1
                            position += 1
                    End Select

                    If equation.Chars(position) = "(" Then
                        position += 1
                        mBracketNode = New node(equation, position, Me, 0)
                        If position + 2 < equation.Length AndAlso equation.Chars(position + 2) <> ")" Then
                            mOperationNode = New node(equation, position, Me, Count + 1)
                        End If
                        'mParentNode.mOperationNode = 
                        'GoTo start
                        'Return
                    End If

                    strTmpValue = ""
readStr:            If equation.Length > position AndAlso _
                        (equation.Chars(position) <> "*" _
                        And equation.Chars(position) <> "-" _
                        And equation.Chars(position) <> "+" _
                        And equation.Chars(position) <> "^" _
                        And equation.Chars(position) <> "/" _
                        And equation.Chars(position) <> ")" _
                        And equation.Chars(position) <> "(") _
                    Then
                        strTmpValue &= equation.Chars(position)
                        position += 1
                        GoTo readStr
                    End If
                    If strTmpValue <> "" Then
                        Dim rgxMatch As New System.Text.RegularExpressions.Regex("^[A-Z][A-Z0-0]*")
                        If rgxMatch.IsMatch(strTmpValue) Then
                            Me.mVariable = alu_valueType.VARIABLE_LOOKUP
                            For i As Byte = 0 To 255
                                If System.Enum.GetName(GetType(BikSettings.TableVariables), i) = "BCTV_" & strTmpValue Then
                                    mValueVariable = i
                                    Exit For
                                End If
                            Next
                            If mValueVariable = BikSettings.TableVariables.BCTV_UNDEFINED Then
                                Throw New ArathmaticUnit_Exception("Invalid Variable")
                            End If
                        Else
                            Me.mVariable = alu_valueType.FLOAT
                            mValue = Val(strTmpValue)
                        End If
                    End If

                    If position < equation.Length AndAlso _
                        (equation.Chars(position) = "*" _
                        Or equation.Chars(position) = "-" _
                        Or equation.Chars(position) = "+" _
                        Or equation.Chars(position) = "/" _
                        Or equation.Chars(position) = "^") _
                    Then
                        mOperationNode = New node(equation, position, Me, Count + 1)
                        ' Return
                    End If

                    'GoTo start
                End If
            End If
        End Sub

        Public Function isValid() As Boolean

        End Function
        Public Sub Clean()
            If isBracket Then
                mBracketNode.Clean()
                If Not hasOperation And Not IsNothing(mParentNode) AndAlso Not mParentNode.isBracket Then
                    ' Delete Self!
                    mBracketNode.mParentNode = Me.mParentNode
                    If Me.mParentNode.mBracketNode Is Me Then
                        mParentNode.mBracketNode = mBracketNode
                    Else
                        mParentNode.mOperationNode = mBracketNode
                    End If
                End If
            End If
            If mCount >= 2 Then
                Dim tmpNode As New node(mParentNode.mParentNode, mParentNode.mParentNode.mParentNode, Me)
                Dim tmpUnRefNode As node = mParentNode
                mParentNode = tmpNode
                tmpUnRefNode.mOperationNode = Nothing
                tmpUnRefNode.mParentNode.mParentNode.mBracketNode = tmpNode
                tmpUnRefNode.mParentNode.mParentNode = tmpNode
                Me.mCount = 1
            End If
            If hasOperation Then
                mOperationNode.Clean()
            End If
        End Sub
        Public Sub Assign(ByRef ALUNum As Integer)
            mAluNum = ALUNum
            If hasOperation Then
                mOperationNode.Assign(ALUNum)
            End If
            If isBracket Then
                ALUNum += 1
                mBracketNode.Assign(ALUNum)
            End If
        End Sub
        ''' <summary>
        ''' Function outputs its calcualtions into a bikirom usable array
        ''' its assumed that the offset is zero for the primary caclculation
        ''' and that everything is in 9 byte offsets per chunk as per excel
        ''' document.
        ''' </summary>
        ''' <param name="arrByte"></param>
        ''' <remarks></remarks>
        Public Sub Output(ByRef arrByte As Byte())
            ' Always do the bracket operations first
            If isBracket Then
                mBracketNode.Output(arrByte)
                Me.mVariable = alu_valueType.VARIABLE_LOOKUP
            End If
            If mAluNum >= 0 Then
                ' Do Self operation
                Dim offset As Integer = mAluNum * 9

                If mCount = 1 Then
                    If Me.mVariable = alu_valueType.FLOAT Then
                        arrByte(offset + 8) = arrByte(offset + 8) Or 2
                    ElseIf Me.mVariable = alu_valueType.VARIABLE_LOOKUP Then
                        arrByte(offset + 8) = arrByte(offset + 8) And &HFD
                    End If
                    arrByte(offset + 8) = (Me.mOperation << 2) Or (arrByte(offset + 8) And &H3)
                    offset += 4
                Else
                    If Me.mVariable = alu_valueType.FLOAT Then
                        arrByte(offset + 8) = arrByte(offset + 8) Or 1
                    ElseIf Me.mVariable = alu_valueType.VARIABLE_LOOKUP Then
                        arrByte(offset + 8) = arrByte(offset + 8) And &HFE
                    End If
                End If

                If isBracket() Then
                    arrByte(offset) = Me.mBracketNode.mAluNum + BikSettings.TableVariables.BCTV_AL1
                Else
                    If Me.mVariable = alu_valueType.FLOAT Then
                        Array.Copy(System.BitConverter.GetBytes(mValue), 0, arrByte, offset, 4)
                    ElseIf Me.mVariable = alu_valueType.VARIABLE_LOOKUP Then
                        arrByte(offset) = Me.mValueVariable
                    End If
                End If
            End If
            ' Do opertion
            If hasOperation Then
                mOperationNode.Output(arrByte)
            End If
        End Sub
        Public Function Units(Optional ByVal AU As Integer = 0) As Integer
            If isBracket Then
                AU = Me.mBracketNode.Units(AU)
            End If
            If Me.mAluNum > AU Then
                AU = Me.mAluNum
            End If
            If hasOperation Then
                AU = Me.mOperationNode.Units(AU)
            End If
            Return AU
        End Function

        ''' <summary>
        ''' Is a bracket is it links to another node via the bracket node link
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property isBracket() As Boolean
            Get
                If Not IsNothing(mBracketNode) Then
                    Return True
                Else
                    Return False
                End If
            End Get
        End Property
        Public ReadOnly Property hasOperation() As Boolean
            Get
                If IsNothing(mOperationNode) Then
                    Return False
                Else
                    Return True
                End If
            End Get
        End Property

        Public Function getBracketNode() As node
            Return mBracketNode
        End Function
        Public Function getOperaterionNode() As node
            Return mOperationNode
        End Function

        Public Property operation() As alu_operation
            Get
                Return mOperation
            End Get
            Set(ByVal value As alu_operation)
                mOperation = value
            End Set
        End Property
        Public Property value() As Single
            Get
                Return mValue
            End Get
            Set(ByVal value As Single)
                mValue = value
            End Set
        End Property
        Public Property variables() As alu_valueType
            Get
                Return mVariable
            End Get
            Set(ByVal value As alu_valueType)
                mVariable = value
            End Set
        End Property

        Public Sub Print(ByRef Indent As String)
            '' Prints to console the variables!
            Console.WriteLine(Indent & "Value: " & mValue & " Operation: " & System.Enum.GetName(GetType(alu_operation), mOperation) & " Variable: " & mVariable & " Count: " & mCount)
            If Not IsNothing(mOperationNode) Then
                Console.WriteLine(Indent & "+++Has Operation")
                mOperationNode.Print(Indent)
            End If
            If Not IsNothing(mBracketNode) Then
                Console.WriteLine(Indent & "---Has Brackets")
                Indent &= Chr(9)
                mBracketNode.Print(Indent)
                Indent = Indent.Substring(0, Indent.Length - 1)
            End If
        End Sub
        Public Sub Draw(ByRef pt As System.Drawing.Point, ByRef e As System.Drawing.Graphics)
            Dim sngWidth As Single = 0
            Dim sz As New System.Drawing.Size(30, 30)
            Dim strText As String = ""
            Dim fntText As System.Drawing.Font = New System.Drawing.Font("Courier New", 14, System.Drawing.GraphicsUnit.Pixel)
            Dim fillColour As System.Drawing.Brush = Drawing.Brushes.Black

            If hasOperation Then
                If mOperationNode.isBracket Then
                    If isBracket Then sngWidth = mBracketNode.DrawWidth
                    sngWidth = 4 + (2 * sngWidth)

                    e.DrawLine(Drawing.Pens.Black, CSng(pt.X), CSng(pt.Y + (sz.Height / 2)), CSng(pt.X + sz.Width + (sz.Width * sngWidth)), CSng(pt.Y + (sz.Height / 2)))

                    pt.X += sz.Width * sngWidth
                    mOperationNode.Draw(pt, e)
                    pt.X -= sz.Width * sngWidth
                Else
                    e.DrawLine(Drawing.Pens.Black, CSng(pt.X), CSng(pt.Y + (sz.Height / 2)), CSng(pt.X + sz.Width + (sz.Width * 2)), CSng(pt.Y + (sz.Height / 2)))
                    pt.X += sz.Width * 2
                    mOperationNode.Draw(pt, e)
                    pt.X -= sz.Width * 2
                End If
            End If

            If isBracket Then
                fillColour = System.Drawing.Brushes.Blue
                e.DrawLine(Drawing.Pens.Black, CSng(pt.X + (sz.Width / 2)), CSng(pt.Y), CSng(pt.X + (sz.Width / 2)), CSng(pt.Y + sz.Height + (sz.Height * 2)))
                pt.Y += sz.Height * 2
                mBracketNode.Draw(pt, e)
                pt.Y -= sz.Height * 2
            End If

            e.FillRectangle(fillColour, New System.Drawing.RectangleF(pt, sz))

            Select Case Me.operation
                Case alu_operation.ADDITION
                    strText = "+"
                Case alu_operation.DIVIDE
                    strText = "/"
                Case alu_operation.MULTIPLY
                    strText = "*"
                Case alu_operation.POWEROF
                    strText = "^"
                Case alu_operation.SUBTRACTION
                    strText = "-"
            End Select

            If Me.mVariable = alu_valueType.FLOAT Then
                strText &= Me.mValue
            ElseIf Me.mVariable = alu_valueType.VARIABLE_LOOKUP Then
                strText &= System.Enum.GetName(GetType(BikSettings.TableVariables), Me.mValueVariable)
            End If

            e.DrawString(strText, fntText, Drawing.Brushes.White, pt)

            e.DrawString("A:" & mAluNum, fntText, Drawing.Brushes.Coral, New System.Drawing.PointF(pt.X, pt.Y + 16))

        End Sub
        Public Function DrawWidth() As Single
            Dim sngValue As Single = 0
            If isBracket And hasOperation AndAlso mOperationNode.isBracket Then
                sngValue = 2 + mOperationNode.DrawWidth
            End If
            If hasOperation AndAlso mOperationNode.hasOperation Then
                sngValue += 1 + mOperationNode.DrawWidth
            ElseIf hasOperation Then
                sngValue += mOperationNode.DrawWidth
            End If
            If isBracket Then
                sngValue += mBracketNode.DrawWidth
            End If
            Return sngValue
        End Function

        Public Function CalculateOperation(ByVal value As Single) As Single
            Console.WriteLine("Value: " & mValue & " Operation: " & System.Enum.GetName(GetType(alu_operation), mOperation) & " Variable: " & mVariable)
            Dim sngValue As Single = 0
            Select Case mOperation
                Case alu_operation.ADDITION
                    sngValue = value + mValue
                Case alu_operation.DIVIDE
                    sngValue = value / mValue
                Case alu_operation.MULTIPLY
                    sngValue = value * mValue
                Case alu_operation.SUBTRACTION
                    sngValue = value - mValue
            End Select
            Return sngValue
        End Function
        Public Function Calculate(Optional ByVal value As Single = 0) As Single
            Console.WriteLine("CALC: Value: " & mValue & " Operation: " & System.Enum.GetName(GetType(alu_operation), mOperation) & " Variable: " & mVariable)
            Dim sngValue As Single = 0
            Dim bCalcBypass As Boolean = False
            ' calculate all brackets first

            If isBracket Then
                mValue = mBracketNode.Calculate()
                Console.WriteLine("Return from bracket: " & mValue)
            End If

            If hasOperation Then
                sngValue = mOperationNode.Calculate()
                Console.WriteLine("Return from operation: " & sngValue)
                bCalcBypass = True
            End If
            If isBracket And hasOperation AndAlso mOperationNode.isBracket Then
                Console.WriteLine("END CALCULATION")
                Select Case mOperationNode.operation
                    Case alu_operation.ADDITION
                        sngValue = mValue + sngValue
                    Case alu_operation.DIVIDE
                        sngValue = mValue / sngValue
                    Case alu_operation.MULTIPLY
                        sngValue = mValue * sngValue
                    Case alu_operation.POWEROF
                        sngValue = mValue ^ sngValue
                    Case alu_operation.SUBTRACTION
                        sngValue = mValue - sngValue
                End Select
            End If

            If isBracket And Not IsNothing(mParentNode) AndAlso Not mParentNode.isBracket Then
            Else
                If isBracket And Not hasOperation Then
                    Return mValue
                End If
            End If


            If (Not isBracket Or (isBracket And hasOperation AndAlso mOperationNode.isBracket)) And Not bCalcBypass _
            Or (isBracket And Not IsNothing(mParentNode) AndAlso Not mParentNode.isBracket) Then
                Select Case mOperation
                    Case alu_operation.ADDITION
                        sngValue = mParentNode.value + mValue
                    Case alu_operation.DIVIDE
                        sngValue = mParentNode.value / mValue
                    Case alu_operation.MULTIPLY
                        sngValue = mParentNode.value * mValue
                    Case alu_operation.POWEROF
                        sngValue = mParentNode.value ^ mValue
                    Case alu_operation.SUBTRACTION
                        sngValue = mParentNode.value - mValue
                End Select
            End If

            Return sngValue
        End Function

    End Class

    Public Class ArathmaticUnit_Exception
        Inherits System.Exception

        Private mDescription As String = ""
        Private mLocation As Integer = 0

        Sub New(ByVal description As String, Optional ByVal location As Integer = 0)
            mDescription = description
            mLocation = location
        End Sub

        Public ReadOnly Property Description() As String
            Get
                Return mDescription
            End Get
        End Property
        Public ReadOnly Property Location() As Integer
            Get
                Return mLocation
            End Get
        End Property
    End Class
End Namespace